/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package modeloprediccion;

import java.sql.*;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import motorprediccion.DatoPosicional;
import motorprediccion.Parada;
import motorprediccion.Recorrido;

/**
 *
 * @author diego
 */
public class ModeloPredictorCorrector extends Modelo{

    Long toleranciaDiferenciaTiempo = new Long(this.getParametro("tolerancia_diferencia_tiempo"));
    Long toleranciaTiempoBase = new Long(this.getParametro("tolerancia_tiempo_base"));
    Float ponderacionNuevoDato = new Float(this.getParametro("ponderacion_nuevo_dato"));

    @Override
    public void actualizarValoresParametros(){
        this.toleranciaDiferenciaTiempo = new Long(this.getParametro("tolerancia_diferencia_tiempo"));
        this.ponderacionNuevoDato = new Float(this.getParametro("ponderacion_nuevo_dato"));
        this.toleranciaTiempoBase = new Long(this.getParametro("tolerancia_tiempo_base"));
    }
    
    public Integer procesarNuevosTiempos(ArrayList<DatoPosicional> posicionesNuevas){
        //funcion que calcula las nuevas predicciones de acuerdo a las nuevas posicionesNuevas
        Integer cantActualizaciones = 0;
        try {
            Long fecha_Actualizacion= new Long(this.getParametro("fecha_actualizacion"));
            if ((new GregorianCalendar().getTimeInMillis() - fecha_Actualizacion) >= this.toleranciaTiempoBase) //cambian condicion de acuerdo a periodicidad de tiempo base
                this.calcularPrediccionBase();
            for (; cantActualizaciones < posicionesNuevas.size(); cantActualizaciones++){
                this.procesarNuevoTiempo(posicionesNuevas.get(cantActualizaciones));
            }
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        this.setParametro("fecha_actualizacion", String.valueOf(new GregorianCalendar().getTimeInMillis()));
        return cantActualizaciones;
    }

    public Double obtenerPrediccion(Integer linea, Integer origen, Integer destino){
        //funcion que retorna el tiempo de error entre dos paradas
        Double prediccion = new Double(0);
        try {
            Connection conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            String consulta = "SELECT predictorcorrector.tiempo_estimado,predictorcorrector.tiempo_espera FROM "+
                    "public.predictorcorrector, public.parada "+
                    "WHERE predictorcorrector.parada_origen = parada.idparada AND "+
                    "parada.linea_id = "+ linea +
                    "AND parada.orden >= " + origen + "AND parada.orden < "+ destino+";\n";
            Statement s= conexionBD.createStatement();
            ResultSet resultado = s.executeQuery(consulta);
            while(resultado.next()) {
                prediccion += (resultado.getDouble("tiempo_estimado")+resultado.getDouble("tiempo_espera"));
            }
            conexionBD.close();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        return prediccion;
    }
    
    public void calcularPrediccionBase() throws SQLException{
        //Aca inicia la carga de tiempos desde la BD
        Connection conexionBD;
        Statement s;
        ResultSet resultado;
        HashMap< Integer, Double> predicciones = new HashMap< Integer, Double>();
        HashMap< Integer, Double> esperas = new HashMap< Integer, Double>();
        String fecha_actualizacion = this.getParametro("fecha_actualizacion");
        GregorianCalendar fecha = new GregorianCalendar();
        //Calculo de tiempos de a una linea por vez            
        try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                        "postgres", "postgres");
            s = conexionBD.createStatement();            
            for (int i=0; i<recorridos.size(); i++){
                //mejorar para que tome solo los datos de la linea elegida y de los ultimos dias

                Recorrido rTemp = this.recorridos.get(i);
                String consulta = "SELECT \"vehicleId\", latitud, longitud, "+
                    "\"fechaConsulta\"+\"secsSinceLastReport\"*1000 as fecha "+
                    "FROM motorprediccion.posiciones_crudo, public.unidad " +
                    "WHERE posiciones_crudo.\"vehicleId\" = unidad.id_unidad AND unidad.linea_id = " + rTemp.getId() +
                    "AND predictable = 'true' AND \"fechaConsulta\" >= " + fecha_actualizacion +" order by fecha asc;";
                
                resultado = s.executeQuery(consulta);
                
                while(resultado.next()) {
                    Float lat = resultado.getFloat("latitud"), lon = resultado.getFloat("longitud");
                    Integer idColectivo = resultado.getInt("vehicleId");
                    fecha.setTimeInMillis(resultado.getLong("fecha"));
                    DatoPosicional posicionTemp = new DatoPosicional(idColectivo, lat,lon,fecha);
                    Parada nuevaParada = null, Temp2 = null;
                //Aca empieza el calculo de tiempos
                    if (this.posiciones.isEmpty() || this.posiciones.get(idColectivo) == null){ //es el primer dato del colectivo
                        posicionTemp.setParadaAsociada(0);
                        this.posiciones.put(idColectivo, posicionTemp);
                        nuevaParada = rTemp.encontrarParadaCercana(posicionTemp.getPosicion(), 0);
                        if (nuevaParada != null){
                            System.out.println("nuevo cole: "+ posicionTemp.getIdColectivo() +" posicion: " + nuevaParada.getOrden());
                            this.posiciones.get(posicionTemp.getIdColectivo()).setParadaAsociada(nuevaParada.getOrden());
                        }
                    }
                    else{ 
                        nuevaParada = rTemp.encontrarParadaCercana(posicionTemp.getPosicion(), this.posiciones.get(idColectivo).getParadaAsociada());
                        if (nuevaParada != null){
                            Long diferenciaTiempo = posicionTemp.getFecha().getTimeInMillis() - this.posiciones.get(idColectivo).getFecha().getTimeInMillis();
                            if (diferenciaTiempo < this.toleranciaDiferenciaTiempo){ 
                                Parada Temp1 = rTemp.obtenerParadaOrden(this.posiciones.get(idColectivo).getParadaAsociada());
                                if (Temp1.getOrden() == nuevaParada.getOrden()){
                                    if (esperas.containsKey(Temp1.getId()))
                                        esperas.put(Temp1.getId(), diferenciaTiempo * this.ponderacionNuevoDato + (1-this.ponderacionNuevoDato) * esperas.get(Temp1.getId()));
                                    else
                                        esperas.put(Temp1.getId(), new Double(diferenciaTiempo * this.ponderacionNuevoDato));
                                }
                                else{
                                    Double diferenciaDistancia = rTemp.distancia(Temp1.getOrden(), nuevaParada.getOrden());
                                    for (int paso = this.posiciones.get(idColectivo).getParadaAsociada(); paso < nuevaParada.getOrden(); paso++){
                                        Temp2 = rTemp.obtenerParadaOrden(Temp1.getOrden()); //el orden de la parada es uno mayor a su posicion en el arreglo
                                        Double prediccion = diferenciaTiempo * rTemp.distancia(Temp1, Temp2)/ diferenciaDistancia;
                                        if (predicciones.containsKey(Temp1.getId()))
                                            predicciones.put(Temp1.getId(), prediccion * this.ponderacionNuevoDato + (1-this.ponderacionNuevoDato) * predicciones.get(Temp1.getId()));
                                        else
                                            predicciones.put(Temp1.getId(), prediccion * this.ponderacionNuevoDato);
                                        Temp1 = Temp2;
                                    }
                                }
                            }
                            posicionTemp.setParadaAsociada(nuevaParada.getOrden()-1);
                            this.posiciones.put(idColectivo, posicionTemp);
                        }
                    }
                }

                //Guardar los datos en la BD
                for (int paradaActual = 0; paradaActual < rTemp.getCantidadParadas();paradaActual++){
                    if (predicciones.containsKey(rTemp.obtenerParadaOrden(paradaActual).getId()) && !(predicciones.get(rTemp.obtenerParadaOrden(paradaActual).getId()).isInfinite())){
                        this.guardarPrediccion(rTemp.obtenerParadaOrden(paradaActual), predicciones.get(rTemp.obtenerParadaOrden(paradaActual).getId()));
                        this.guardarEspera(rTemp.obtenerParadaOrden(paradaActual), esperas.get(rTemp.obtenerParadaOrden(paradaActual).getId()));
                    }
                    else{
                        this.guardarPrediccion(rTemp.obtenerParadaOrden(paradaActual), new Double(0));
                        this.guardarEspera(rTemp.obtenerParadaOrden(paradaActual), new Double(0));
                    }
                }
            }
        }
        catch (SQLException se) {
            System.out.println("SQL Error");
            se.printStackTrace();
            System.exit(1);
        }
    }
        
    public void guardarPrediccion(Parada parada, Double prediccion) throws SQLException {
        //Esta funcion guarda una estimacion en la Base de Datos
        Connection conexionBD = null;
        String valor = null;
        try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            Statement s = conexionBD.createStatement();
            String consulta = "SELECT predictorcorrector.tiempo_estimado FROM "+
                "public.predictorcorrector WHERE predictorcorrector.parada_origen = "+ parada.getId() + ";\n";
            ResultSet resultado = s.executeQuery(consulta);
            while(resultado.next()) {
                valor = resultado.getString("tiempo_estimado");
            }
            if (valor == null){
                consulta = "INSERT INTO predictorcorrector(parada_origen, "+
                        "fecha_actualizacion, tiempo_estimado, error_estimado, tiempo_espera) " +
                        "VALUES (" + parada.getId() + ", " + 
                        "to_timestamp("+new GregorianCalendar().getTimeInMillis()/1000 +"), " +
                        prediccion + ", 0, 0);\n";
            }
            else{
                consulta = "UPDATE predictorcorrector SET " +
                    "fecha_actualizacion = to_timestamp("+new GregorianCalendar().getTimeInMillis()/1000 +"), "
                    + "tiempo_estimado= "+ prediccion +  
                    " WHERE parada_origen = "+ parada.getId()+";\n";
            }
            s.executeUpdate(consulta);
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        conexionBD.close();
    }
    
        public Double obtenerError(Integer linea, Parada origen, Parada destino){
        //funcion que retorna el tiempo de error entre dos paradas
        Double error = new Double(0);
        try {
            Connection conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            String consulta = "SELECT predictorcorrector.error_estimado FROM "+
                    "public.predictorcorrector, public.parada "+
                    "WHERE predictorcorrector.parada_origen = parada.idparada AND "+
                    "parada.linea_id = "+ linea +
                    "AND parada.orden >= " + origen.getOrden() + "AND parada.orden < "+ destino.getOrden()+";\n";
            Statement s= conexionBD.createStatement();
            ResultSet resultado = s.executeQuery(consulta);
            while(resultado.next()) {
                error += resultado.getDouble("error_estimado");
            }
            conexionBD.close();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        return error;
    }
        
    Integer buscarRecorridoColectivo(Integer idColectivo){
        Integer idLinea = 0;
        //funcion que busca el id a partir de un identificador de un idcolectivo
        try {
            Connection conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            String consulta = "SELECT unidad.linea_id FROM public.unidad WHERE "+
                    "unidad.id_unidad = "+ idColectivo+";\n";
            Statement s= conexionBD.createStatement();
            ResultSet resultado = s.executeQuery(consulta);
            while(resultado.next())
                idLinea = resultado.getInt("linea_id");
            conexionBD.close();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        return idLinea; 
    }
    
    public void guardarEspera(Parada parada, Double espera) throws SQLException {
        //Esta funcion guarda una estimacion en la Base de Datos
        Connection conexionBD = null;
        
        try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            Statement s = conexionBD.createStatement();
            String consulta = "UPDATE predictorcorrector SET " +
                    "fecha_actualizacion= to_timestamp("+new GregorianCalendar().getTimeInMillis()/1000 +"), "
                    + "tiempo_espera= "+ espera +  
                    " WHERE parada_origen = "+ parada.getId()+";\n";
            s.executeUpdate(consulta);
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        conexionBD.close();
    }

    public Double obtenerEspera(Integer linea, Parada origen, Parada destino){
        //funcion que retorna el tiempo de espera entre dos paradas
        Double espera = new Double(0);
        try {
            Connection conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            String consulta = "SELECT predictorcorrector.tiempo_espera FROM "+
                    "public.predictorcorrector, public.parada "+
                    "WHERE predictorcorrector.parada_origen = parada.idparada AND "+
                    "parada.linea_id = "+ linea +
                    "AND parada.orden >= " + origen.getOrden() + "AND parada.orden < "+ destino.getOrden()+";\n";
            Statement s= conexionBD.createStatement();
            ResultSet resultado = s.executeQuery(consulta);
            while(resultado.next()) {
                espera += resultado.getDouble("tiempo_espera");
            }
            conexionBD.close();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        return espera;
    }
    
    public Integer procesarNuevoTiempo(DatoPosicional posicion){
        Integer indexRecorrido = new Integer(0), idRecorrido = this.buscarRecorridoColectivo(posicion.getIdColectivo());
        Parada nuevaParada;

        while (indexRecorrido < this.recorridos.size() && this.recorridos.get(indexRecorrido).getId() != idRecorrido)
            indexRecorrido++;
        if (indexRecorrido == this.recorridos.size()) 
            return -1; //No existe o no se encontro recorrido
        else {
            Recorrido rTemp = this.recorridos.get(indexRecorrido);
            if (this.posiciones.get(posicion.getIdColectivo()) == null){ //es el primer dato del colectivo
                this.posiciones.put(posicion.getIdColectivo(), posicion);
                this.posiciones.get(posicion.getIdColectivo()).setParadaAsociada(0);
                nuevaParada = rTemp.encontrarParadaCercana(posicion.getPosicion(), 0);
                if (nuevaParada != null){
                    this.posiciones.get(posicion.getIdColectivo()).setParadaAsociada(nuevaParada.getOrden());
                }
            }
            else{
                nuevaParada = rTemp.encontrarParadaCercana(posicion.getPosicion(), this.posiciones.get(posicion.getIdColectivo()).getParadaAsociada());
                if (nuevaParada != null){
                    long diferenciaTiempo = posicion.getFecha().getTimeInMillis() - this.posiciones.get(posicion.getIdColectivo()).getFecha().getTimeInMillis();
                    if (diferenciaTiempo < this.toleranciaDiferenciaTiempo){ 
                        Parada Temp1 = rTemp.obtenerParadaOrden(this.posiciones.get(posicion.getIdColectivo()).getParadaAsociada());
                        try {
                            if (nuevaParada.getOrden() == Temp1.getOrden()){ 
                                this.guardarEspera(Temp1, diferenciaTiempo * this.ponderacionNuevoDato + (1-this.ponderacionNuevoDato) * this.obtenerEspera(rTemp.getId(), Temp1, nuevaParada));
                            }
                            else {
                                Double diferenciaDistancia = rTemp.distancia(Temp1.getOrden(), nuevaParada.getOrden());
                                for (int paso = this.posiciones.get(posicion.getIdColectivo()).getParadaAsociada(); paso < nuevaParada.getOrden(); paso++){
                                    Parada Temp2 = rTemp.obtenerParadaOrden(Temp1.getOrden()); //el orden de la parada es uno mayor a su posicion en el arreglo
                                    Double prediccion = diferenciaTiempo * rTemp.distancia(Temp1, Temp2)/ diferenciaDistancia;
                                    Double prediccionDB = this.obtenerPrediccion(rTemp.getId(),Temp1.getOrden(), Temp2.getOrden());
                                    Double errorDB = this.obtenerError(rTemp.getId(), Temp1, Temp2);
                                    this.guardarPrediccion(Temp1, prediccion* this.ponderacionNuevoDato + (1-this.ponderacionNuevoDato) * prediccionDB);
                                    this.guardarError(Temp1, (prediccion - prediccionDB)* this.ponderacionNuevoDato + (1-this.ponderacionNuevoDato) *errorDB);
                                    Temp1 = Temp2;
                                }
                            posicion.setParadaAsociada(nuevaParada.getOrden()-1);
                            posiciones.put(posicion.getIdColectivo(), posicion);
                            }
                        }
                        catch (SQLException se) {
                            System.out.println("Error al guardar prediccion para parada "+ Temp1.getId());
                        }
                    }
                }
            }
        }
        return 0;
    }
    
        public Double obtenerPrediccionSinEspera(Integer linea, Parada origen, Parada destino){
        //funcion que retorna el tiempo de error entre dos paradas
        Double prediccion = new Double(0);
        try {
            Connection conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            String consulta = "SELECT predictorcorrector.tiempo_estimado FROM "+
                    "public.predictorcorrector, public.parada "+
                    "WHERE predictorcorrector.parada_origen = parada.idparada AND "+
                    "parada.linea_id = "+ linea +
                    "AND parada.orden >= " + origen.getOrden() + "AND parada.orden < "+ destino.getOrden()+";\n";
            Statement s= conexionBD.createStatement();
            ResultSet resultado = s.executeQuery(consulta);
            while(resultado.next()) {
                prediccion += resultado.getDouble("tiempo_estimado");
            }
            conexionBD.close();
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        return prediccion;
    }

    public void guardarError(Parada parada, Double error) throws SQLException {
        //Esta funcion guarda una estimacion en la Base de Datos
        Connection conexionBD = null;
        
        try {
            conexionBD = DriverManager.getConnection("jdbc:postgresql://localhost/sistemaprediccion",
                                            "postgres", "postgres");
            Statement s = conexionBD.createStatement();
            String consulta = "UPDATE predictorcorrector SET " +
                    "fecha_actualizacion = to_timestamp("+new GregorianCalendar().getTimeInMillis()/1000 +"), "
                    + "error_estimado = "+ error +  
                    " WHERE parada_origen = "+ parada.getId()+";\n";
            s.executeUpdate(consulta);
        }
        catch (SQLException se) {
          System.out.println("Error en la BD");
          se.printStackTrace();
          System.exit(1);
        }
        conexionBD.close();
    }
}
